隔离设计对应的单词是 Bulkheads,中文翻译为隔板。但其实,这个术语是用在造船上的,也就是船舱里防漏水的隔板。一般的船无论大小都会有这个东西,大一点的船都会把船舱隔成若干个空间。这样,如果船舱漏水,只会进到一个小空间里,不会让整个船舱都进水而导致整艘船都沉了,如下图所示。
我们的软件设计当然也“漏水”,所以为了不让这个“故障”蔓延开来,需要使用“隔板”技术,来将架构分隔成多个“船舱”来隔离故障。
多扯一句,著名的泰坦尼克号也有 Bulkheads 设计,然而其设计上有个缺陷。如下图所示,当其撞上冰山漏水时,因为船体倾斜,导到水漫过了隔板,从而下沉了。
在分布式软件架构中,我们同样需要使用类似这样的技术来让我们的故障得到隔离。这就需要我们对系统进行分离。一般来说,对于系统的分离有两种方式,一种是以服务的种类来做分离,一种是以用户来做分离。下面具体说明一下这两种方式。
下面这个图中,说明了按服务种类来做分离的情况。
上图中,我们将系统分成了用户、商品、社区三个版块。三个板块分别使用不同的域名、服务器和数据库,做到从接入层到应用层再到数据层三层完全隔离。这样一来,在物理上来说,一个版块的故障就不会影响到另一版块。
在亚马逊,每个服务都有自己的一个数据库,每个数据库中都保存着和这个业务相关的数据和相应的处理状态。而每个服务从一开始就准备好了对外暴露。同时,这也是微服务所推荐的架构方式。
然而任何架构都有其好和不好的地方,上面这种架构虽然在系统隔离上做得比较好,但是也存在以下一些问题。
对于这样的问题,一般来说,我们需要小心地设计用户交互,最好不要让用户在一个页面上获得所有的数据。对于目前的手机端上来说,因为手机屏幕尺寸比较小,所以,也不可能在一个屏幕页上展示太多的内容。
对于这个问题,一方面,我们需要保证这个业务流程中各个子系统的高可用性,并且在业务流程上做成 Step-by-Step 的方式,这样用户交互的每一步都可以保存,以便故障恢复后可以继续执行,而不是从头执行。
也就是说,先做一个 plan 的 API 调用,然后各个子系统 reserve 住相应的资源,如果成功,则 Commit;如果有一个失败,则整体 Cancel。这其实很像阿里的 TCC – try confirm/cancel。
可见,隔离了的系统在具体的业务场景中还是有很多问题的,是需要我们小心和处理的。对此,我们不可掉以轻心。根据我的经验,这样的系统通常会引入大量的异步处理模型。
下图是一个按用户请求来做分离的图示。
在这个图中,可以看到,我们将用户分成不同的组,并把后端的同一个服务根据这些不同的组分成不同的实例。让同一个服务对于不同的用户进行冗余和隔离,这样一来,当服务实例挂掉时,只会影响其中一部分用户,而不会导致所有的用户无法访问。
这种分离和上面按功能的分离可以融合。说白了,这就是所谓的“多租户”模式。对于一些比较大的客户,我们可以为他们设置专门独立的服务实例,或是服务集群与其他客户隔离开来,对于一些比较小的用户来说,可以让他们共享一个服务实例,这样可以节省相关的资源。
对于“多租户”的架构来说,会引入一些系统设计的复杂度。一方面,如果完全隔离,资源使用上会比较浪费,如果共享,又会导致程序设计的一些复杂度。
通常来说多租户的做法有三种。
这三种方案各有优缺点,如图所示。
通过上图,可以看到:
所以,一般来说,技术方案会使用折衷方案,也就是中间方案,服务是共享的,数据通过分区来隔离,而对于一些比较重要的租户(需要好的隔离性),则使用完全独立的方式。
然而,在虚拟化技术非常成熟的今天,我们完全可以使用“完全独立”(完全隔离)的方案,通过底层的虚拟化技术(Hypervisor 的技术,如 KVM,或是 Linux Container 的技术,如 Docker)来实现物理资源的共享和成本的节约。
要能做好隔离设计,我们需要有如下的一些设计考量。
好了,我们来总结一下今天分享的主要内容。首先,我从船体水密舱的设计,引出了分布式系统设计中的隔离设计。然后我介绍了常见的隔离有两种,一种是按服务种类隔离,另一种是按用户隔离(即多租户)。下篇文章中,我们讲述异步通讯设计。希望对你有帮助。
也欢迎你分享一下你是如何为分布式系统做隔离设计的。